#include "imageacquisitor.h"
#include "ui_imageacquisitor.h"
#include "generatorinterface.h"
#include "global.h"
#include "detectorinterface.h"
#include "procedureinterface.h"
#include "imageeditdialog.h"
#include "ftpclient.h"

#include "dcmtk/config/osconfig.h"
#include "dcmtk/dcmdata/dcmetinf.h"
#include "dcmtk/dcmdata/dcfilefo.h"
#include "dcmtk/dcmdata/dcdatset.h"
#include "dcmtk/dcmdata/dcdeftag.h"
#include "dcmtk/dcmdata/dcuid.h"

#include <QPluginLoader>
#include <QDateTime>
#include <QDebug>
#include <QDir>
#include <QSettings>
#include <QTextCodec>
#include <QLocalServer>
#include <QLocalSocket>
#include <QSqlDatabase>
#include <QSqlDriver>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>

ImageAcquisitor::ImageAcquisitor(QWidget *parent) :
    server(new QLocalServer(this)),
    encoder(0),
    ff(new DcmFileFormat),
    curSeriesNo(1),
    curImageNo(1),
    imgBuf(0),
    genIntf(0),
    detIntf(0),
    procIntf(0),
    ftp(new FtpClient(this)),
    edit(new ImageEditDialog(this)),
    QWidget(parent),
    ui(new Ui::ImageAcquisitor)
{
    ui->setupUi(this);
    loadPlugin();
    initFileFormat();
    startLocalServer();
    initDatabase();
}

ImageAcquisitor::~ImageAcquisitor()
{
    delete encoder;
    delete ff;
    delete imgBuf;
    delete ui;
}

void ImageAcquisitor::startLocalServer()
{
    QStringList args = QApplication::arguments();
    args.removeFirst();
    QLocalSocket *s = new QLocalSocket;
    connect(s, SIGNAL(disconnected()), s, SLOT(deleteLater()));
    s->connectToServer(QApplication::applicationName());
    if (s->waitForConnected(500)) {
        foreach (const QString &a, args) {
            s->write(a.toLocal8Bit());
            s->write(";");
        }
        s->flush();
        s->waitForBytesWritten();
        QEventLoop l;
        connect(s, SIGNAL(disconnected()), &l, SLOT(quit()));
        l.exec();
    } else {
        if (!args.isEmpty()) {
            studyUid = args.first();
            server->listen(QApplication::applicationName());
            startAcq();
        }
    }
}

void ImageAcquisitor::onNewConnection()
{
    QLocalSocket *s = server->nextPendingConnection();
    if (s && s->waitForReadyRead()) {
        QByteArray b = s->readAll();
        QStringList args = QString::fromLocal8Bit(b).split(";", QString::SkipEmptyParts);
        if (!args.isEmpty()) {
            studyUid = args.first();
            startAcq();
        }
    }
    s->deleteLater();
}

void ImageAcquisitor::initDatabase()
{
    QSettings s(QCoreApplication::applicationDirPath() + "/"LOCAL_NETWORK_CONFIG, QSettings::IniFormat);
    s.setIniCodec(QSETTINGS_CODEC_NAME);

    QString type = s.value(NETWORK_DBMS_TYPE).toString();
    QSqlDatabase d = QSqlDatabase::addDatabase(type);
    if (d.isValid()) {
        d.setHostName(s.value(NETWORK_DBMS_HOST).toString());
        d.setPort(s.value(NETWORK_DBMS_PORT).toInt());
        d.setDatabaseName(s.value(NETWORK_DBMS_NAME).toString());
        d.setUserName(s.value(NETWORK_DBMS_USER).toString());
        d.setPassword(s.value(NETWORK_DBMS_PWD).toString());
        if (d.open()) {
            showMessage(tr("Database connected."));
        } else {
            showMessage(tr("Connect to database failed."));
        }
    } else {
        showMessage(tr("Invalid database type: ") + type);
    }
}

void ImageAcquisitor::startAcq()
{
    QSqlQuery q(QSqlDatabase::database());
    q.prepare("SELECT * FROM study_t WHERE study_iuid=%1");
    q.addBindValue(studyUid);
    if (q.exec()) {
        if (q.next()) {
            rec = q.record();
            ui->patientNameEdit->setText(rec.value("pati_name").toString());
            ui->patientIdEdit->setText(rec.value("pati_id").toString());
            ui->patientBirthEdit->setText(rec.value("pati_age").toString());
            ui->patientSexEdit->setText(rec.value("pati_sex").toString());
            curSeriesNo = 1;
            seriesUid.clear();
            curImageNo = 1;
            insertStudyInfo();
            if (detIntf) {
                detIntf->startCapture(true);
            } else {
                showMessage(tr("Detector plugin not loaded."));
            }
        } else {
            showMessage(tr("Study not found: ")+studyUid);
        }
    } else {
        showMessage(q.lastError().text());
    }
}

void ImageAcquisitor::endAcq()
{
    studyUid.clear();
    seriesUid.clear();
    curSeriesNo = 1;
    curImageNo = 1;
    rec.clear();
    close();
}

void ImageAcquisitor::getImage()
{
    delete[] imgBuf;
    if (detIntf) {
        if (detIntf->getImage(imgBuf, width, height)) {
            char uid[128];
            QString cuid;
            dcmGenerateUniqueIdentifier(uid, SITE_INSTANCE_UID_ROOT);
            imageUid = QString::fromLatin1(uid);
            DcmMetaInfo *meta = ff->getMetaInfo();
            cuid = detConf.specifics.processUid;
            if (!cuid.isEmpty()) meta->putAndInsertString(DCM_MediaStorageSOPClassUID, cuid.toLatin1().data());
            meta->putAndInsertString(DCM_MediaStorageSOPInstanceUID, uid);

            DcmDataset *dset = ff->getDataset();

            if (!cuid.isEmpty()) dset->putAndInsertString(DCM_SOPClassUID, cuid.toLatin1().data());
            dset->putAndInsertString(DCM_SOPInstanceUID, uid);

            dset->putAndInsertString(DCM_SeriesNumber, QString::number(curSeriesNo).toLatin1().data());
            QDateTime t = QDateTime::currentDateTime();
            if (seriesUid.isEmpty()) seriesUid = QString::fromLatin1(dcmGenerateUniqueIdentifier(uid, SITE_SERIES_UID_ROOT));
            dset->putAndInsertString(DCM_SeriesInstanceUID, seriesUid.toLatin1().data());
            dset->putAndInsertString(DCM_SeriesDate, t.date().toString(DICOM_DATE_FORMAT).toLatin1().data());
            dset->putAndInsertString(DCM_SeriesTime, t.time().toString(DICOM_TIME_FORMAT).toLatin1().data());

            dset->putAndInsertString(DCM_InstanceNumber, QString::number(curImageNo).toLatin1().data());
            dset->putAndInsertString(DCM_AcquisitionDate, t.date().toString(DICOM_DATE_FORMAT).toLatin1().data());
            dset->putAndInsertString(DCM_AcquisitionTime, t.time().toString(DICOM_TIME_FORMAT).toLatin1().data());
            dset->putAndInsertString(DCM_ContentDate, t.date().toString(DICOM_DATE_FORMAT).toLatin1().data());
            dset->putAndInsertString(DCM_ContentTime, t.time().toString(DICOM_TIME_FORMAT).toLatin1().data());

            if (procIntf) {
                QString e, p;
                procIntf->getBodyPartAndViewPos(e, p);
                if ((!exam.isEmpty()) && exam != e) {
                    curSeriesNo++;
                    seriesUid.clear();
                    curImageNo = 1;
                }
                exam = e;

                dset->putAndInsertString(DCM_BodyPartExamined, encoder->fromUnicode(e).data());
                dset->putAndInsertString(DCM_ViewPosition, encoder->fromUnicode(p).data());

                ushort bits = 16;
                dset->findAndGetUint16(DCM_BitsStored, bits);
                procIntf->setImage(imgBuf, width, height, bits);
            } else {
                showMessage(tr("Procedure plugin not loaded."));
            }

            if (genIntf) {
                double kv, ma, ms, mx;
                genIntf->getDoseValues(kv, ma, ms, mx);
                dset->putAndInsertString(DCM_KVP, QString::number(kv, 'f', 6).toLatin1().data());
                dset->putAndInsertString(DCM_XRayTubeCurrent, QString::number(ma, 'f', 6).toLatin1().data());
                if (mx > 0) dset->putAndInsertString(DCM_Exposure, QString::number(mx, 'f', 6).toLatin1().data());
                else dset->putAndInsertString(DCM_ExposureTime, QString::number(ms, 'f', 6).toLatin1().data());
            } else {
                showMessage(tr("Generator plugin not loaded."));
            }

            dset->putAndInsertString(DCM_NumberOfFrames, "1");
            dset->putAndInsertUint16(DCM_Columns, width);
            dset->putAndInsertUint16(DCM_Rows, height);
            dset->putAndInsertUint16Array(DCM_PixelData, imgBuf, width * height);

            OFCondition cond;
            QString raw = ftp->getTempPath()+"/"+imageUid+".raw";
            cond = ff->saveFile(raw.toLocal8Bit().data(), ff->getDataset()->getOriginalXfer());
            if (cond.bad()) {
                QString msg = tr("Save raw image file failed.");
                showMessage(msg);
                qCritical() << msg;
            }

            edit->setFileFormat(ff);
            bool rl = detConf.transform.rotLeft;
            bool rr = detConf.transform.rotRight;
            bool fh = detConf.transform.flipHori;
            bool fv = detConf.transform.flipVert;
            edit->setTransform(rl, rr, fh, fv);
            if (procIntf) {
                if (procIntf->getTransform(rl, rr, fh, fv))
                    edit->setTransform(rl, rr, fh, fv);
            }
            if (edit->exec() == QDialog::Accepted) {
                DcmDataset *dset = ff->getDataset();
                QString cuid = detConf.specifics.presentUid;
                if (!cuid.isEmpty()) dset->putAndInsertString(DCM_SOPClassUID, cuid.toLatin1().data());

                QString dcm = ftp->getTempPath() + "/" + imageUid + ".dcm";
                OFCondition cond;
                cond = ff->saveFile(dcm.toLocal8Bit().data());
                if (cond.good()) {
                    ftp->put(raw, studyUid+"/"+imageUid+".raw");
                    ftp->put(dcm, studyUid+"/"+imageUid+".dcm");
                } else {
                    QString msg = tr("Save image to file failed.");
                    showMessage(msg);
                    qCritical() << msg;
                }

                if (detIntf) detIntf->startCapture(true);
            } else {

            }
            edit->clear();
        } else {
            showMessage(tr("Null image data."));
        }
    } else {
        showMessage(tr("Detector plugin not loaded."));
    }
}

void ImageAcquisitor::getEnhancedImage()
{
    if (procIntf) {
        delete[] imgBuf;
        imgBuf = 0;
        if (procIntf->getEnhancedImage(imgBuf, width, height)) {
            DcmDataset *dset = ff->getDataset();
            dset->putAndInsertUint16(DCM_Columns, width);
            dset->putAndInsertUint16(DCM_Rows, height);
            dset->putAndInsertUint16Array(DCM_PixelData, imgBuf, width*height);

            edit->setFileFormat(ff);
            double c, w;
            procIntf->getWindow(c, w);
            edit->setWindow(c, w);
            QRect r;
            procIntf->getClipRect(r);
            edit->setClipRect(r);

            delete[] imgBuf;
            imgBuf = 0;
        } else {
            showMessage(tr("Image enhance failed."));
        }
    }
}

void ImageAcquisitor::loadPlugin()
{
    QPluginLoader l;
    l.setFileName("generator");
    genIntf = qobject_cast<GeneratorInterface*>(l.instance());
    if (genIntf) {
        ui->vLayout->insertWidget(1, qobject_cast<QWidget*>(genIntf->getObject()));
        genIntf->setGeneratorHook(PluginProc, this);
    }

    l.setFileName("detector");
    detIntf = qobject_cast<DetectorInterface*>(l.instance());
    if (detIntf) {
        ui->vLayout->insertWidget(3, qobject_cast<QWidget*>(detIntf->getObject()));
        detIntf->setDetectorHook(PluginProc, this);
    }

    l.setFileName("procedure");
    procIntf = qobject_cast<ProcedureInterface*>(l.instance());
    if (procIntf) {
        ui->hLayout->insertWidget(1, qobject_cast<QWidget*>(procIntf->getObject()));
        procIntf->setProcedureHook(PluginProc, this);
        double kv, ma, ms, mx;
        procIntf->getDoseValues(kv, ma, ms, mx);
        updateDoseValues(kv, ma, ms, mx);
    }
}

void ImageAcquisitor::insertStudyInfo()
{
    QString str;
    DcmDataset *dset = ff->getDataset();
    QDateTime t = QDateTime::fromString(rec.value("study_time").toString(), DATETIME_FORMAT);
    if (t.isValid()) {
        dset->putAndInsertString(DCM_StudyDate, t.date().toString(DICOM_DATE_FORMAT).toLatin1().data());
        dset->putAndInsertString(DCM_StudyTime, t.time().toString(DICOM_TIME_FORMAT).toLatin1().data());
    }
    str = rec.value("study_id").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_StudyID, encoder->fromUnicode(str).data());
    str = rec.value("acc_num").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_AccessionNumber, encoder->fromUnicode(str).data());
    str = rec.value("pati_id").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_PatientID, encoder->fromUnicode(str).data());
    str = rec.value("pati_name").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_PatientName, encoder->fromUnicode(str).data());
    QDate d = QDate::fromString(rec.value("pati_brith").toString(), DATE_FORMAT);
    if (d.isValid()) dset->putAndInsertString(DCM_PatientBirthDate, d.toString(DICOM_DATE_FORMAT).toLatin1().data());
    str = rec.value("pati_adid").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_AdmissionID, encoder->fromUnicode(str).data());
    str = rec.value("pati_alert").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_MedicalAlerts, encoder->fromUnicode(str).data());
    str = rec.value("req_phy").toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_RequestingPhysician, encoder->fromUnicode(str).data());
}

void ImageAcquisitor::initFileFormat()
{
    DcmMetaInfo *meta = ff->getMetaInfo();
    if (gLocalByteOrder == EBO_BigEndian) {
        meta->putAndInsertString(DCM_TransferSyntaxUID, UID_BigEndianExplicitTransferSyntax);
    } else {
        meta->putAndInsertString(DCM_TransferSyntaxUID, UID_LittleEndianExplicitTransferSyntax);
    }

    DcmDataset *dset = ff->getDataset();
    QString str;
    QSettings sys(LOCAL_SYSTEM_CONFIG, QSettings::IniFormat);
    sys.setIniCodec(QSETTINGS_CODEC_NAME);

    QTextCodec *codec = QTextCodec::codecForName(sys.value(SYSTEM_STATION_CHARACTERSET).toByteArray());
    if (!codec) codec = QTextCodec::codecForLocale();
    encoder = codec->makeEncoder();
    str = sys.value(SYSTEM_STATION_CHARACTERSET).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_SpecificCharacterSet, str.toLatin1().data());
    else dset->putAndInsertString(DCM_SpecificCharacterSet, "ISO_IR 100");
    str = sys.value(SYSTEM_MANUFACTURE_NAME).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_Manufacturer, encoder->fromUnicode(str).data());
    str = sys.value(SYSTEM_MANUFACTURE_MODEL).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_ManufacturerModelName, encoder->fromUnicode(str).data());
    str = sys.value(SYSTEM_STATION_NAME).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_StationName, encoder->fromUnicode(str).data());
    str = sys.value(SYSTEM_INSTITUTION_NAME).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_InstitutionName, encoder->fromUnicode(str).data());
    str = sys.value(SYSTEM_INSTITUTION_ADDR).toString();
    if (!str.isEmpty()) dset->putAndInsertString(DCM_InstitutionAddress, encoder->fromUnicode(str).data());

    if (detIntf && detIntf->getDetectorConfig(detConf)) {
        str = detConf.id;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_DetectorType, encoder->fromUnicode(str).data());
        str = detConf.id;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_DetectorID, encoder->fromUnicode(str).data());
        str = detConf.specifics.imageType;
        str.replace(",", "\\");
        if (!str.isEmpty()) dset->putAndInsertString(DCM_ImageType, str.toLatin1().data());
        str = detConf.specifics.modality;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_Modality, str.toLatin1().data());
        str = detConf.specifics.conversionType;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_ConversionType, str.toLatin1().data());
        str = QString("%1\\%2").arg(detConf.specifics.pixelHeight, 0, 'f', 6).arg(detConf.specifics.pixelWidth, 0, 'f', 6);
        if (!str.isEmpty()) dset->putAndInsertString(DCM_ImagerPixelSpacing, str.toLatin1().data());
        if (!str.isEmpty()) dset->putAndInsertString(DCM_PixelSpacing, str.toLatin1().data());
        str = detConf.specifics.interpret;
        dset->putAndInsertString(DCM_PhotometricInterpretation, str.toLatin1().data());
        dset->putAndInsertUint16(DCM_BitsAllocated, detConf.specifics.bitsAllocated);
        dset->putAndInsertUint16(DCM_BitsStored, detConf.specifics.bitsStored);
        dset->putAndInsertUint16(DCM_HighBit, detConf.specifics.highBit);
        dset->putAndInsertUint16(DCM_SamplesPerPixel, detConf.specifics.samplesPerPixel);
        if (!str.isEmpty()) dset->putAndInsertUint16(DCM_PixelRepresentation, detConf.specifics.pixelRepresentation);
        str = QString::number(detConf.specifics.rescaleIntercept);
        if (!str.isEmpty()) dset->putAndInsertString(DCM_RescaleIntercept,str.toLatin1().data());
        str = QString::number(detConf.specifics.rescaleSlope);
        if (!str.isEmpty()) dset->putAndInsertString(DCM_RescaleSlope, str.toLatin1().data());
        str = detConf.specifics.rescaleType;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_RescaleType, encoder->fromUnicode(str).data());
        str = detConf.specifics.explanation;
        if (!str.isEmpty()) dset->putAndInsertString(DCM_WindowCenterWidthExplanation, encoder->fromUnicode(str).data());
    } else {
        showMessage(tr("Error while getting detector configuration."));
    }
}

void ImageAcquisitor::showMessage(const QString &msg)
{
    ui->textBrowser->append(QDateTime::currentDateTime().toString(DATETIME_FORMAT) + ": " + msg);
}

void ImageAcquisitor::saveDoseValues(const double &kv, const double &ma, const double &ms, const double &mx)
{
    procIntf->setDoseValues(kv, ma, ms, mx);
}

void ImageAcquisitor::updateDoseValues(const double &kv, const double &ma, const double &ms, const double &mx)
{
    genIntf->setDoseValues(kv, ma, ms, mx);
}

void ImageAcquisitor::PluginProc(const QJsonObject &json, void *userData)
{
    ImageAcquisitor *w = qobject_cast<ImageAcquisitor*>((QObject*)userData);
    if (w) {
        QString act = json["action"].toString();
        w->showMessage(act);
        if (act == "ShowMessage") {
            w->showMessage(json["args"].toString());
        } else if (act == "SaveDoseValues") {
            double kv, ma, ms, mx;
            QJsonObject arg = json["args"].toObject();
            kv = arg["kv"].toDouble();
            ma = arg["ma"].toDouble();
            ms = arg["ms"].toDouble();
            mx = arg["mx"].toDouble();
            w->saveDoseValues(kv, ma, ms, mx);
        } else if (act == "UpdateDoseValues") {
            double kv, ma, ms, mx;
            QJsonObject arg = json["args"].toObject();
            kv = arg["kv"].toDouble();
            ma = arg["ma"].toDouble();
            ms = arg["ms"].toDouble();
            mx = arg["mx"].toDouble();
            w->updateDoseValues(kv, ma, ms, mx);
        } else if (act == "GetImage") {
            w->getImage();
        } else if (act == "GetEnhancedImage") {
            w->getEnhancedImage();
        } else if (act == "EndStudy") {
            w->endAcq();
        }
    }
}
